// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared.DataFlow;

namespace ILLink.RoslynAnalyzer.DataFlow
{
    // Tracks the set of methods which get analyzed together during interprocedural analysis,
    // and the possible states of hoisted locals in state machine methods and lambdas/local functions.
    public struct InterproceduralState<TValue, TValueLattice> : IEquatable<InterproceduralState<TValue, TValueLattice>>
        where TValue : struct, IEquatable<TValue>
        where TValueLattice : ILattice<TValue>
    {
        public ValueSet<MethodBodyValue> Methods;

        // The HoistedLocals dictionary has a default value of MaybeLattice.Top (effectively null),
        // for any local that has not been discovered to be captured by a nested function.
        // Once we discover that a local is captured, it gets the value TValueLattice.Top
        // (in our case the "empty" MultiValue), and from then on reading/writing the local will use this
        // dictionary instead of the per-method dictionary.
        public DefaultValueDictionary<LocalKey, Maybe<TValue>> HoistedLocals;

        private readonly InterproceduralStateLattice<TValue, TValueLattice> lattice;

        public InterproceduralState(
            ValueSet<MethodBodyValue> methods,
            DefaultValueDictionary<LocalKey, Maybe<TValue>> hoistedLocals,
            InterproceduralStateLattice<TValue, TValueLattice> lattice)
        {
            Methods = methods;
            HoistedLocals = hoistedLocals;
            this.lattice = lattice;
        }

        public bool Equals(InterproceduralState<TValue, TValueLattice> other)
            => Methods.Equals(other.Methods) && HoistedLocals.Equals(other.HoistedLocals);

        public override bool Equals(object obj)
            => obj is InterproceduralState<TValue, TValueLattice> inst && Equals(inst);

        public override int GetHashCode()
            => throw new NotImplementedException();

        public InterproceduralState<TValue, TValueLattice> Clone()
            => new(Methods.DeepCopy(),
            HoistedLocals.Clone(), lattice);

        public void TrackMethod(MethodBodyValue method)
        {
            Debug.Assert(!Methods.IsUnknown());
            var methodsList = new List<MethodBodyValue>(Methods.GetKnownValues());
            methodsList.Add(method);
            Methods = new ValueSet<MethodBodyValue>(methodsList);
        }

        public void TrackHoistedLocal(LocalKey key)
        {
            var existingValue = HoistedLocals.Get(key);
            if (existingValue.MaybeValue != null)
                return; // Already tracked

            HoistedLocals.Set(key, new Maybe<TValue>(lattice.HoistedLocalLattice.ValueLattice.ValueLattice.Top));
        }

        public bool TrySetHoistedLocal(LocalKey key, TValue value)
        {
            var existingValue = HoistedLocals.Get(key);
            if (existingValue.MaybeValue == null)
                return false;

            // For hoisted locals, we track the entire set of assigned values seen
            // in the closure of a method, so setting a hoisted local value meets
            // it with any existing value.
            HoistedLocals.Set(key,
                lattice.HoistedLocalLattice.ValueLattice.Meet(
                    existingValue, new(value)));
            return true;
        }

        public bool TryGetHoistedLocal(LocalKey key, [NotNullWhen(true)] out TValue? value)
            => (value = HoistedLocals.Get(key).MaybeValue) != null;
    }

    public struct InterproceduralStateLattice<TValue, TValueLattice> : ILattice<InterproceduralState<TValue, TValueLattice>>
        where TValue : struct, IEquatable<TValue>
        where TValueLattice : ILattice<TValue>
    {
        public readonly ValueSetLattice<MethodBodyValue> MethodLattice;

        public readonly DictionaryLattice<LocalKey, Maybe<TValue>, MaybeLattice<TValue, TValueLattice>> HoistedLocalLattice;

        public InterproceduralStateLattice(
            ValueSetLattice<MethodBodyValue> methodLattice,
            DictionaryLattice<LocalKey, Maybe<TValue>, MaybeLattice<TValue, TValueLattice>> hoistedLocalLattice
        )
        {
            MethodLattice = methodLattice;
            HoistedLocalLattice = hoistedLocalLattice;
        }

        public InterproceduralState<TValue, TValueLattice> Top => new(MethodLattice.Top,
            HoistedLocalLattice.Top, this);

        public InterproceduralState<TValue, TValueLattice> Meet(InterproceduralState<TValue, TValueLattice> left, InterproceduralState<TValue, TValueLattice> right)
            => new(
                MethodLattice.Meet(left.Methods, right.Methods),
                HoistedLocalLattice.Meet(left.HoistedLocals, right.HoistedLocals),
                this);
    }
}
